package com.ccb.sc.common.modules.common.service.impl;

import com.ccb.sc.common.modules.common.service.IRedisService;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.stereotype.Service;

import java.time.Duration;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * <p>
 * 序列号服务实现类
 * </p>
 * @author ken
 * @version 1.0
 * @date 2020-09-15
 */
@Service
public class RedisServiceImpl implements IRedisService {

    private RedisTemplate<String, Object> redisTemplate;

    @Override
    public void set(String key, Object value) {
        this.redisTemplate.opsForValue().set(key, value);
    }

    @Override
    public void set(String key, Object value, long timeout) {
        this.redisTemplate.opsForValue().set(key, value, timeout, TimeUnit.SECONDS);
    }

    @Override
    public void set(String key, Object value, long timeout, TimeUnit unit) {
        this.redisTemplate.opsForValue().set(key, value, timeout, unit);
    }

    @Override
    public Object get(String key) {
        return this.redisTemplate.opsForValue().get(key);
    }

    @Override
    public void setWithDayClean(String key, Object value) {
        Duration duration = Duration.between(LocalDateTime.now(), LocalDate.now().plusDays(1).atTime(0, 0 ,1));
        //计算过期时间，为第二天的凌晨0点0分，为防止误差，增加1秒延时
        long span = duration.getSeconds();

        this.set(key, value, span, TimeUnit.SECONDS);
    }

    @Override
    public void expire(String key, long timeout, TimeUnit unit) {
        this.redisTemplate.expire(key, timeout, unit);
    }

    @Override
    public void expireWithDayClean(String key) {
        Duration duration = Duration.between(LocalDateTime.now(), LocalDate.now().plusDays(1).atTime(0, 0 ,1));
        //计算过期时间，为第二天的凌晨0点0分，为防止误差，增加1秒延时
        long span = duration.getSeconds();
        this.expire(key, span, TimeUnit.SECONDS);
    }

    @Override
    public void delete(String key) {
        this.redisTemplate.delete(key);
    }

    @Override
    public void delete(Set<String> keys) {
        this.redisTemplate.delete(keys);
    }

    @Override
    public long getNextByDayClean(String identityKey) {
        Duration duration = Duration.between(LocalDateTime.now(), LocalDate.now().plusDays(1).atTime(0, 0 ,1));
        //计算过期时间，为第二天的凌晨0点0分，为防止误差，增加2秒延时
        long span = duration.getSeconds();

        try {
            long value = this.redisTemplate.opsForValue().increment(identityKey, 1);
            long expireTime = this.redisTemplate.getExpire(identityKey);
            if (expireTime <= 0) {
                this.redisTemplate.expire(identityKey, span, TimeUnit.SECONDS);
            }
            return value;
        } catch (Exception e) {
            this.redisTemplate.opsForValue().set(identityKey, 0, span, TimeUnit.SECONDS);
            return this.redisTemplate.opsForValue().increment(identityKey, 1);
        }
    }

    @Override
    public String getNextStandardCodeByDayClean(String key, int sLength) {
        key = key == null ? "default" : key;

        long n = getNextByDayClean("serialNumber:" + key);

        DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyyMMddHHmmss");
        String format = dtf.format(LocalDateTime.now());

        String serialNumber = StringUtils.leftPad(String.valueOf(n), sLength, "0");
        int v = new Random().nextInt(100);
        String s = StringUtils.leftPad(v + "", 2, "0");
        return key + format + serialNumber + s;
    }

    @Override
    public long getNext(String key) {
        try {
            return redisTemplate.opsForValue().increment(key, 1);
        } catch (Exception e) {
            redisTemplate.opsForValue().set(key, 0);

            return redisTemplate.opsForValue().increment(key, 1);
        }
    }

    @Override
    public <T> T execute(RedisScript<T> script, List<String> keys, Object... args) {
        try {
            return (T) this.redisTemplate.execute(script, keys, args);
        }catch (Exception e) {
            return (T) this.redisTemplate.execute(script, keys, args);
        }
    }

    @Override
    public Set<String> keys(String key) {
        return this.redisTemplate.keys(key);
    }

    @Override
    public boolean zAdd(String key, Object value, double score) {
        return this.redisTemplate.opsForZSet().add(key, value, score);
    }

    @Override
    public Set<ZSetOperations.TypedTuple<Object>> rangeWithScores(String key, long start, long end, boolean isReverse) {
        if (isReverse) {
            return this.redisTemplate.opsForZSet().reverseRangeWithScores(key, start, end);
        }
        return this.redisTemplate.opsForZSet().rangeWithScores(key, start, end);
    }

    @Override
    public boolean lock(String key, Object value, int expireSecond) {
        String luaScripts = "if redis.call('setnx',KEYS[1],ARGV[1]) == 1 then" +
                " redis.call('expire',KEYS[1],ARGV[2]) return 1 else return 0 end";
        RedisScript<Long> redisScript = new DefaultRedisScript<>(luaScripts, Long.class);
        Long result = execute(redisScript, Collections.singletonList(key), value, expireSecond);
        //判断是否成功
        return Objects.equals(result, 1L);
    }

    @Override
    public boolean unLock(String key, Object value) {
        String luaScripts = "if redis.call('get',KEYS[1]) == ARGV[1] then" +
                " return redis.call('del',KEYS[1]) else return 0 end";
        RedisScript<Long> redisScript = new DefaultRedisScript<>(luaScripts, Long.class);
        Long result = execute(redisScript, Collections.singletonList(key), value);
        //判断是否成功
        return Objects.equals(result, 1L);
    }

    @Autowired
    public void setRedisTemplate(RedisTemplate redisTemplate) {
        this.redisTemplate = redisTemplate;
    }
}
